package plgutils

import (
	"bytes"
	"dgo/MyProj/PrjHttpClt"
	"dgo/MyProj/PrjHttpUtils"
	"dgo/goutils/utils"
	"dgo/plg/plgnet"
	"dgo/plg/yredis"
	"fmt"
	"gitee.com/ymofen/supervalue"
	"strings"
	"time"
)

/*
*

	对象存储仓库
	  对象有数量
	  借出对象有一个sessionid

	可以作为基类
*/
type ObjStoragePlg struct {
	onlinecnt    int32
	sess_obj_cnt int32

	PlgBase
	statDoy    int
	maxOnline  int32
	matchCycle int8 // 1:循环匹配, 分流匹配(默认)

	msglst    *utils.SyncCycleList
	objectlst utils.GSyncMapGroup // id: objPojo
	sesslst   utils.GSyncMapGroup // sessid: session
	tasklst   utils.GSyncMapGroup
	redisOp   *yredis.RedisInner
	innerHttp *plgnet.InnerHttp

	releaseTimeout time.Duration

	url             string
	last_reloadurl  string
	localfile       string
	last_warn       string
	last_reload_msg string
	last_data       string

	cron              *utils.Cron
	sch               *utils.CronSchedule
	cronExp           string
	lastreloadt       time.Time
	lastuptime_reload time.Time
	startuptime       time.Time
	notifyEx          *PlgBaseInnerEx
}

func (this *ObjStoragePlg) RemoveUserPojo(usr string) {
	this.objectlst.RemoveAndGet(usr)
}

func (this *ObjStoragePlg) ReloadDatalist(dataBuf []byte, reloadall int) int {
	this.last_data = string(dataBuf)
	json, err := wrapper.NewSVFromBuf(dataBuf)
	if err != nil {
		this.last_warn = fmt.Sprintf("[%s]数据解析异常:%s, 忽略加载", utils.NowString(), err.Error())
		return -1
	}

	if json.IntByName("code", 0) == -1 {
		this.last_warn = fmt.Sprintf("[%s]数据加载异常:%s, err:%s", utils.NowString(), json.StringByName("msg", json.String()))
		return -1
	}

	if json != nil && json.IsObject() {
		json = json.ValueByName("list")
	}

	if json == nil {
		this.last_warn = fmt.Sprintf("[%s]list为nil, 忽略加载", utils.NowString())
		return -1
	}

	cnt := 0
	/*
		[
		   {

		   }
		]
	*/
	json.Range(func(key string, val *wrapper.SuperValue) bool {
		id := val.StringByName("id", "")
		if len(id) == 0 {
			return true
		}

		itm := this.checkGetPojo(id, true)
		itm.ReloadData(val)
		itm.loadflag = 1
		cnt++
		return true
	})

	if !json.IsArray() {
		this.last_warn = fmt.Sprintf("[%s]list非法的数组对象, 忽略加载", utils.NowString())
		return -1
	}

	if reloadall == 1 {
		this.objectlst.Range(func(key, value interface{}) bool {
			itm := value.(*ObjStoragePojo)
			itm.lk.Lock()
			if itm.loadflag == 0 {
				if itm.UsageNum == 0 {
					this.objectlst.Remove(key)
				} else {
					itm.State = 0
				}
			}
			itm.loadflag = 0
			itm.lk.Unlock()
			return true
		})
	}

	return cnt
}

func (this *ObjStoragePlg) ReloadData(reloadall int) {
	t0 := time.Now()
	this.last_reload_msg = fmt.Sprintf("[%s]开始进行重载", utils.NowString())
	succ_n := 0
	reloadflag := false
	defer func() {
		if reloadflag {
			this.last_reload_msg = fmt.Sprintf("[%s]帐号进行了重载, 成功加载:%d, 有效账号:%d，耗时:%s, url:%s", utils.NowString(), succ_n, this.objectlst.Count(), utils.DurationStr(time.Since(t0)), this.last_reloadurl)
		} else {
			this.last_reload_msg = fmt.Sprintf("[%s]帐号数据未发生变化，有效账号:%d，耗时:%s, url:%s", utils.NowString(), this.objectlst.Count(), utils.DurationStr(time.Since(t0)), this.last_reloadurl)
		}
	}()
	var dataBuf, newBuf []byte
	if len(this.localfile) > 0 && utils.FileIsExists(this.localfile) {
		buf, err := utils.ReadFile(this.localfile)
		if err != nil {
			this.LogWarnMessage("读取文件异常:%s, file:%s", err.Error(), this.localfile)
		} else {
			dataBuf = buf
		}
	}
	if len(this.url) > 0 {
		url := this.url
		lastuptime := "1980-01-01 00:00:00"
		if reloadall == 0 && !this.lastuptime_reload.IsZero() {
			lastuptime = utils.DateTimeString2(this.lastuptime_reload)
		}
		if strings.Contains(url, "$lastuptime$") {
			url = strings.ReplaceAll(url, "$lastuptime$", utils.UrlEncodeStr(lastuptime))
		} else {
			reloadall = 1
		}

		this.last_reloadurl = url
		buf, err := PrjHttpClt.GetUrl(url)
		if err != nil {
			this.last_warn = fmt.Sprintf("获取数据异常:%s, url:%s", err.Error(), url)
			this.LogWarnMessage("获取数据异常:%s, file:%s", err.Error(), url)
		}
		if len(buf) > 0 {
			if reloadall == 0 {
				dataBuf = buf // 发生变化
				newBuf = buf
			} else if bytes.Compare(dataBuf, buf) != 0 {
				dataBuf = buf // 发生变化
				newBuf = buf
			}
		}
	}

	if len(dataBuf) == 0 {
		this.last_warn = fmt.Sprintf("数据不存在")
		return
	}

	if this.objectlst.Count() == 0 || len(newBuf) > 0 || reloadall == 1 { // 没加载过，或者有新的数据
		reloadflag = true
		if this.ReloadDatalist(dataBuf, reloadall) > 0 {
			if reloadall == 1 {
				if len(this.localfile) > 0 && len(newBuf) > 0 { // 保存新的文件， 加载全部时存入新文件
					utils.ForceCreateFilePath(this.localfile)
					utils.RewriteFile(this.localfile, newBuf)
				}
			}
			this.lastuptime_reload = t0.Add(time.Second * -60) // 避免忽略了记录
		}

	}
}

func (this *ObjStoragePlg) checkStat() {
	if this.statDoy != time.Now().Day() {
		if this.maxOnline > 0 {
			this.LogWarnMessage("%s:max-online:%d", utils.DateString(time.Now()), this.maxOnline)
		}
		this.statDoy = time.Now().Day()
		this.maxOnline = this.onlinecnt
	}
}

/*
		{
		  "id": "proxystorage",
		  "type": "utils.objstorage",
		  "conf": {
		     "api-prefix":"/rpxs-proxy/",
		     "url":"http://119.96.169.117:3003/plug/rpxs/status?type=5&json=1&searchval=&listfield=list&online=1",
		     "local-file":"rpxs-proxy-lst.conf",
		     "match-cycle":1,
	         "timeout-secs":60,
		     "reload-cron-exp":"0 0/1",
		     "notify-ex":{"id":"", "topic":""}
		  }
		}
*/
func (this *ObjStoragePlg) ReloadConf(conf *wrapper.SuperValue) error {
	if conf == nil {
		return nil
	}

	if this.redisOp == nil {
		this.redisOp = yredis.NewRedisInner()
	}

	if this.msglst == nil {
		this.msglst = utils.NewSyncCycleList()
	}

	if this.cron == nil {
		this.cron = utils.NewCron()
	}

	if this.notifyEx == nil {
		this.notifyEx = PlgNewInnerEx()
	}
	this.notifyEx.ReloadConf(conf.ValueByName("notify-ex"))

	this.redisOp.ReloadConf(conf.ValueByName("redis"))
	{
		this.redisOp.CheckGetClt()
		err := this.redisOp.Ping()
		if err != nil {
			this.LogWarnMessage("redis ping %s err:%s", this.redisOp.ConnString(), err.Error())
		} else {
			this.LogInfoMessage("redis ping ok, %s", this.redisOp.ConnString())
		}
	}

	this.releaseTimeout = conf.DurationByName("timeout-secs", 60) * time.Second
	if this.releaseTimeout < time.Second {
		this.releaseTimeout = time.Second * 60
	}

	this.matchCycle = conf.Int8ByName("match-cycle", 0)

	if this.innerHttp == nil {
		this.innerHttp = plgnet.NewInnerHttp(this)
	}
	this.innerHttp.ReloadConf(conf)

	this.innerHttp.CheckRegisterJSONApi("get", this.onHttpGet)
	this.innerHttp.CheckRegisterJSONApi("ping", this.onHttpPing)
	this.innerHttp.CheckRegisterJSONApi("release", this.onHttpRelease)

	conf.CheckExistsStrFunc("reload-cron-exp", func(strv string) {
		if this.cronExp != strv {
			if this.sch != nil {
				this.sch.Stop()
				this.sch = nil
			}
			this.cronExp = strv
			sch, err := this.cron.AddSchedule(this.cronExp, func() {
				if !this.lastreloadt.IsZero() && time.Since(this.lastreloadt).Seconds() < 10 {
					this.last_warn = fmt.Sprintf("[%s]corn频繁执行?, lastreloadt:%s", utils.NowString(), utils.DateTimeString2(this.lastreloadt))
					sch := this.sch
					if sch != nil {
						this.LogWarnMessage("corn频繁执行?, lastreloadt:%s, nexttime:%s", utils.DateTimeString2(this.lastreloadt), utils.DateTimeString3(sch.GetNextTime()))
					}
					return
				}
				this.lastreloadt = time.Now()
				utils.DefaultWorkers().PostTaskFunc(this, func() {
					this.ReloadData(0)
				})
			})
			if err != nil {
				this.LogWarnMessage("时间表达式异常:%s, err:%s", this.cronExp, err.Error())
			} else {
				this.LogWarnMessage("定时任务开启，下次执行时间:%s", utils.DateTimeString3(sch.GetNextTime()))
				this.sch = sch
			}
		}
	})

	isChanged := false
	conf.CheckStrFunc("url", func(strv string) {
		strv = utils.ReplacePlaceholder(strv, "$$", "$$")
		_, strv = utils.CheckTrimSuffix(strv, "/")
		if this.url != strv {
			this.url = strv
			isChanged = true
		}
	})

	conf.CheckStrFunc("local-file", func(strv string) {
		if this.localfile != strv {
			this.localfile = strv
			isChanged = true
		}
	})

	if isChanged {
		this.ReloadData(1)
	}

	utils.DefaultPatrolTask().AddTask(this, time.Second*10, func(id interface{}, args ...interface{}) {
		utils.DefaultWorkers().PostTaskFunc(this.PlugId, func() {
			this.checkOffline()
			this.checkStat()
		})
	})

	if this.startuptime.IsZero() {
		this.startuptime = time.Now()
	}

	return nil
}

func (this *ObjStoragePlg) Close() (err error) {
	this.redisOp.Close()
	this.PlgBase.Close()
	utils.DefaultPatrolTask().DelTask(this)
	return
}

func (this *ObjStoragePlg) OnHttpStatus(rec *plgnet.PlgHttpRec) (rval interface{}, errcode int, err error) {
	itype := rec.QryAsInt("type", 0)
	if itype == 99 {
		json := wrapper.NewSVObject()
		json.SetKeyValue("sess_obj_cnt", this.sess_obj_cnt)
		json.SetKeyValue("online", this.onlinecnt)
		json.SetKeyValue("peek-online", this.maxOnline)
		json.SetKeyValue("sesslst", this.sesslst.Count())
		json.SetKeyValue("taskcnt", this.tasklst.Count())
		json.SetKeyValue("redis-ops", this.redisOp.StatusString())
		json.SetKeyValue("account-cnt", this.objectlst.Count())
		json.SetKeyValue("url", this.url)
		json.SetKeyValue("reload-msg", this.last_reload_msg)
		json.SetKeyValues("last-warn", this.last_warn)
		json.SetKeyValues("now", utils.NowString())
		if rec.QryAsInt("json", 0) == 1 {
			PrjHttpUtils.ResponseAsJSON(rec.RespW, json.AsJSONStringEx(false, true))
		} else {
			PrjHttpUtils.JSON2TableRec(rec.RespW, "", json.AsJSONString(false))
		}
	} else if itype == 98 {
		rec.ResponseSyncListNEx(this.msglst)
	} else if itype == 97 {
		rec.ResponseText(this.last_data)
	} else if itype == 1 {
		json := wrapper.NewSVArray()
		searchval := rec.QryAsString("searchval", "")
		sn := 0
		maxn := rec.QryAsInt("n", 100)
		this.objectlst.ListRangeV2(func(key, val interface{}) bool {
			pojo := val.(*ObjStoragePojo)
			ok := true
			if len(searchval) > 0 {
				ok = strings.Contains(pojo.Id, searchval) || strings.Contains(pojo.DataStr, searchval)
			}
			return ok
		}, func(k0, v0, k1, v1 interface{}) bool {
			p0 := v0.(*ObjStoragePojo)
			p1 := v1.(*ObjStoragePojo)
			if p0.UsageNum != p1.UsageNum {
				return p0.UsageNum > p1.UsageNum
			}
			return p0.Id < p1.Id
		}, func(key, val interface{}) bool {
			sn++
			pojo := val.(*ObjStoragePojo)
			itm := json.AppendObject()
			itm.SetKeyValue("sn", sn)
			itm.SetKeyValue("id", pojo.Id)
			itm.SetKeyValue("usage", fmt.Sprintf("%d/%d/%d", pojo.UsageNum, pojo.Num, pojo.ObjectGetTimes))
			itm.SetKeyValue("state", pojo.State)
			itm.SetKeyValue("data", pojo.DataStr)
			itm.SetKeyValue("match-partial", pojo.MatchPartial)
			itm.SetKeyValue("block-t", utils.DateTimeString2(pojo.GetBlockT()))
			itm.SetKeyValue("gettimes", fmt.Sprintf("%d[%s]", pojo.ObjectGetTimes, utils.DateTimeString2(pojo.ObjectGetT)))
			itm.SetKeyValue("lastreason", pojo.ReleaseReason)
			itm.SetKeyValue("requestuser", pojo.RequestUserlst.Join(","))

			return sn < maxn
		})

		if rec.QryAsInt("json", 0) == 1 {
			PrjHttpUtils.ResponseAsJSON(rec.RespW, json.AsJSONStringEx(false, false))
		} else {
			PrjHttpUtils.JSON2Table(rec.RespW, "", json.AsJSONString(false))
		}
	} else if itype == 2 {
		json := wrapper.NewSVArray()
		searchval := rec.QryAsString("searchval", "")
		sn := 0
		maxn := rec.QryAsInt("n", 100)
		this.sesslst.ListRangeV2(func(key, val interface{}) bool {
			pojo := val.(*ObjStorageSession)
			ok := true
			if len(searchval) > 0 {
				ok = strings.Contains(pojo.ObjectID, searchval) || strings.Contains(pojo.SessionId, searchval) || strings.Contains(pojo.RequestData, searchval)
			}
			return ok
		}, func(k0, v0, k1, v1 interface{}) bool {
			p0 := v0.(*ObjStorageSession)
			p1 := v1.(*ObjStorageSession)
			if p0.ObjectID != p1.ObjectID {
				return p0.ObjectID < p1.ObjectID
			}
			if p0.StartTime != p1.StartTime {
				return p0.StartTime.Before(p1.StartTime)
			}
			return p0.SessionId < p1.SessionId
		}, func(key, val interface{}) bool {
			sn++
			pojo := val.(*ObjStorageSession)
			itm := json.AppendObject()
			itm.SetKeyValue("sn", sn)
			itm.SetKeyValue("sessionid", pojo.SessionId)
			itm.SetKeyValue("objectid", pojo.ObjectID)
			itm.SetKeyValue("starttime", utils.DateTimeString2(pojo.StartTime))
			itm.SetKeyValue("lasttime", utils.DateTimeString2(pojo.LastTime))
			itm.SetKeyValue("requestdata", pojo.RequestData)
			itm.SetKeyValues("match", pojo.Match)
			itm.SetKeyValue("pingdata", pojo.PingData)
			objPojo := this.checkGetPojo(pojo.ObjectID, false)
			if objPojo != nil {
				itm.SetKeyValues("pojo-match-partial", objPojo.MatchPartial)
			} else {
				itm.SetKeyValues("pojo-match-partial", "")
			}

			return sn < maxn
		})

		if rec.QryAsInt("json", 0) == 1 {
			PrjHttpUtils.ResponseAsJSON(rec.RespW, json.AsJSONStringEx(false, false))
		} else {
			PrjHttpUtils.JSON2Table(rec.RespW, "", json.AsJSONString(false))
		}
	} else if itype == 3 {
		this.objectlst.Range(func(key, value interface{}) bool {
			itm := value.(*ObjStoragePojo)
			itm.RequestUserlst.Reset()
			return true
		})
		rec.ResponseText("OK")
	} else if itype == 4 {
		json := wrapper.NewSVArray()
		searchval := rec.QryAsString("searchval", "")
		sn := 0
		maxn := rec.QryAsInt("n", 100)
		this.tasklst.ListRangeV2(func(key, val interface{}) bool {
			pojo := val.(*debugstepsession)
			ok := true
			if len(searchval) > 0 {
				ok = strings.Contains(pojo.requeststr, searchval)
			}
			return ok
		}, func(k0, v0, k1, v1 interface{}) bool {
			p0 := v0.(*debugstepsession)
			p1 := v1.(*debugstepsession)
			return p0.id < p1.id
		}, func(key, val interface{}) bool {
			sn++
			pojo := val.(*debugstepsession)
			itm := json.AppendObject()
			itm.SetKeyValue("sn", sn)
			itm.SetKeyValue("sessionid", pojo.id)
			itm.SetKeyValue("starttime", utils.DateTimeString2(pojo.startt))
			itm.SetKeyValue("requestdata", pojo.requeststr)
			itm.SetKeyValue("debugstep", pojo.debugstep)
			return sn < maxn
		})

		if rec.QryAsInt("json", 0) == 1 {
			PrjHttpUtils.ResponseAsJSON(rec.RespW, json.AsJSONStringEx(false, false))
		} else {
			PrjHttpUtils.JSON2Table(rec.RespW, "", json.AsJSONString(false))
		}
	} else {
		var sb utils.BytesBuilder
		PrjHttpUtils.AppendIndexBody(&sb, "status", "status?type=99")
		PrjHttpUtils.AppendIndexBody(&sb, "msglist", "status?type=98&n=100&searchval=")
		PrjHttpUtils.AppendIndexBody(&sb, "lastdata", "status?type=97")
		PrjHttpUtils.AppendIndexBody(&sb, "lst", "status?type=1&n=100&selflag=-1&searchval=")
		PrjHttpUtils.AppendIndexBody(&sb, "sesslst", "status?type=2&n=100&selflag=-1&searchval=")
		PrjHttpUtils.AppendIndexBody(&sb, "resetRequestUsers", "status?type=3")
		PrjHttpUtils.AppendIndexBody(&sb, "tasklst", "status?type=4&n=100&selflag=-1&searchval=")
		PrjHttpUtils.ResponseBodyHtml(rec.RespW, sb.String())
	}
	return
}

func (this *ObjStoragePlg) LogInfoMessage(s string, args ...interface{}) {
	this.msglst.LogMsg(s, args...)
	utils.Log.Msgf(this.PlugId, utils.LogLV_INFO, 1, s, args...)
}

func (this *ObjStoragePlg) LogWarnMessage(s string, args ...interface{}) {
	this.msglst.LogMsg(s, args...)
	utils.Log.Msgf(this.PlugId, utils.LogLV_WARN, 1, s, args...)
}

func CreateObjStoragePlgFunc(args ...interface{}) (rval interface{}, err error) {
	obj := &ObjStoragePlg{}

	if len(args) > 0 {
		if str, ok := args[0].(string); ok {
			obj.PlugId = str
		}
	}
	rval = obj
	return
}
